package org.jflame.commons.crypto.digest;

import java.io.IOException;
import java.io.InputStream;
import java.security.DigestException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

import org.jflame.commons.codec.Hex;
import org.jflame.commons.crypto.BC;
import org.jflame.commons.crypto.EncryptException;
import org.jflame.commons.util.CharsetHelper;
import org.jflame.commons.util.IOHelper;

/**
 * 消息摘要加密工具类
 * <p>
 * 哈希算法（Hash）又称摘要算法（Digest），它的作用是：对任意一组输入数据进行计算，得到一个固定长度的输出摘要。<br>
 * 支持算法:md5/SHA-1/SHA-256/SHA512/SM3({@link DigestAlg}). 国密SM3基于BouncyCastleProvider,使用前需引入该包
 * 
 * @see DigestAlg
 * @author zyc
 */
public class DigestHelper {

    private static final int STREAM_BUFFER_LENGTH = 1024;

    /**
     * 返回指定算法名的消息摘要计算实例
     * 
     * @param algorithm 算法名.具体请看
     *            <a href="http://java.sun.com/j2se/1.3/docs/guide/security/CryptoSpec.html#AppA">Appendix A in the Java
     *            Cryptography Architecture API Specification Reference</a>
     * @throws NoSuchAlgorithmException 算法名不支持时异常
     * @return
     */
    public static MessageDigest getDigest(String algorithm) throws NoSuchAlgorithmException {
        return MessageDigest.getInstance(algorithm);
    }

    /**
     * 返回指定算法名的消息摘要计算实例
     * 
     * @param algorithm 算法
     * @return
     * @throws DigestException
     */
    public static MessageDigest getDigest(DigestAlg algorithm) throws DigestException {
        try {
            if (algorithm == DigestAlg.SM3) {
                return MessageDigest.getInstance(algorithm.getName(), BC.addProvider());
            }
            return MessageDigest.getInstance(algorithm.getName());
        } catch (NoSuchAlgorithmException e) {
            throw new DigestException(e);
        } catch (EncryptException e) {
            throw new DigestException(e.getMessage());
        }
    }

    /**
     * 计算消息摘要
     * 
     * @param algorithm 摘要算法
     * @param input 输入
     * @return byte[]
     * @throws DigestException
     */
    public static byte[] digest(DigestAlg algorithm, byte[] input) throws DigestException {
        return getDigest(algorithm).digest(input);
    }

    /**
     * 对一个输入流计算消息摘要
     * 
     * @param algorithm 摘要算法
     * @param inStream 输入流
     * @return byte[]
     * @throws DigestException
     */
    public static byte[] digest(DigestAlg algorithm, InputStream inStream) throws DigestException {
        MessageDigest digest = getDigest(algorithm);
        byte[] buffer = new byte[STREAM_BUFFER_LENGTH];

        try {
            int read = inStream.read(buffer, 0, STREAM_BUFFER_LENGTH);
            while (read > -1) {
                digest.update(buffer, 0, read);
                read = inStream.read(buffer, 0, STREAM_BUFFER_LENGTH);
            }
        } catch (IOException e) {
            throw new DigestException(e);
        } finally {
            IOHelper.closeQuietly(inStream);
        }

        return digest.digest();
    }

    /**
     * 计算字符串消息摘要,返回16进制编码字符串
     * 
     * @param algorithm 摘要算法
     * @param input 输入
     * @return hex编码字符串
     * @throws DigestException
     */
    public static String digestHex(DigestAlg algorithm, String input) throws DigestException {
        return Hex.encodeHexString(getDigest(algorithm).digest(CharsetHelper.getUtf8Bytes(input)));
    }

    /**
     * SHA-1计算消息摘要.
     * 
     * @param plainBytes byte[]
     * @return
     * @throws DigestException
     */
    public static byte[] sha(byte[] plainBytes) throws DigestException {
        return digest(DigestAlg.SHA1, plainBytes);
    }

    /**
     * SHA-1算法计算字符串消息摘要.返回16进制字符串
     * 
     * @param plainText 明文字符串
     * @return hex编码字符串
     * @throws DigestException
     */
    public static String shaHex(String plainText) throws DigestException {
        return digestHex(DigestAlg.SHA1, plainText);
    }

    /**
     * SHA-256算法计算byte[]消息摘要.返回byte[]
     * 
     * @param plainBytes byte[]
     * @return 摘要byte[]
     * @throws DigestException
     */
    public static byte[] sha256(byte[] plainBytes) throws DigestException {
        return digest(DigestAlg.SHA256, plainBytes);
    }

    /**
     * SHA-256算法计算字符串消息摘要.返回16进制字符串
     * 
     * @param plainText 明文字符串
     * @return hex编码字符串
     * @throws DigestException
     */
    public static String sha256Hex(String plainText) throws DigestException {
        return digestHex(DigestAlg.SHA256, plainText);
    }

    /**
     * SHA-512算法计算消息摘要.
     * 
     * @param plainBytes byte[]
     * @return 摘要byte[]
     * @throws DigestException
     */
    public static byte[] sha512(byte[] plainBytes) throws DigestException {
        return digest(DigestAlg.SHA512, plainBytes);
    }

    /**
     * SHA-512算法计算字符串消息摘要.
     * 
     * @param plainText 明文字符串
     * @return 密文16进制字符串
     * @throws DigestException
     */
    public static String sha512Hex(String plainText) throws DigestException {
        return digestHex(DigestAlg.SHA512, plainText);
    }

    /**
     * MD5算法计算消息摘要
     * 
     * @param plainBytes byte[]待计算数据
     * @return MD5 16位byte[]
     * @throws DigestException
     */
    public static byte[] md5(byte[] plainBytes) throws DigestException {
        return digest(DigestAlg.MD5, plainBytes);
    }

    /**
     * MD5算法计算字符串消息摘要，返回16位 <code>byte[]</code>.
     * 
     * @param plainText 明文字符串
     * @return MD5 16位byte[]
     * @throws DigestException
     */
    public static byte[] md5(String plainText) throws DigestException {
        return md5(CharsetHelper.getUtf8Bytes(plainText));
    }

    /**
     * MD5算法计算输入流消息摘要
     * 
     * @param data InputStream 待计算输入流
     * @return byte[] 计算后摘要byte[]
     * @throws IOException 输入流读取异常
     * @throws DigestException
     */
    public static byte[] md5(InputStream data) throws IOException, DigestException {
        return digest(DigestAlg.MD5, data);
    }

    /**
     * MD5算法计算字符串消息摘要，返回32位十六制字符串
     * 
     * @param plainText 字符串
     * @return MD5 32位十六制字符串
     * @throws DigestException
     */
    public static String md5Hex(String plainText) throws DigestException {
        return digestHex(DigestAlg.MD5, plainText);
    }

    /**
     * MD5算法计算byte[]消息摘要，返回32位十六制字符串
     * 
     * @param data byte[]待计算数据
     * @return 32位十六制字符串
     * @throws DigestException
     */
    public static String md5Hex(byte[] data) throws DigestException {
        return Hex.encodeHexString(md5(data));
    }

    /**
     * md5加密字符串,返回16位长度.
     * 
     * @param plainText 明文字符串
     * @return 16进制字符串
     * @throws DigestException
     */
    public static String md5_16(String plainText) throws DigestException {
        return md5Hex(plainText).substring(8, 24);
    }

    /**
     * 国密SM3算法计算字符串摘要
     * 
     * @param data byte[]待计算数据
     * @return
     * @throws DigestException
     */
    public static byte[] sm3(byte[] data) throws DigestException {
        return digest(DigestAlg.SM3, data);
    }

    /**
     * 国密SM3算法计算字符串消息摘要
     * 
     * @param plainText 明文字符串
     * @return
     * @throws DigestException
     */
    public static String sm3Hex(String plainText) throws DigestException {
        return digestHex(DigestAlg.SM3, plainText);
    }

    /**
     * pbkdf2 hash算法,默认参数参考{@link PBKDF2#PBKDF2()}
     * 
     * @param password
     * @param salt
     * @return
     * @throws EncryptException
     */
    public static String pbkdf2(String password, String salt) throws EncryptException {
        PBKDF2 pbk = new PBKDF2();
        return pbk.hash(password, salt);
    }

}
